//-----------------------------------------------------------------------
//  This file is part of the Microsoft Robotics Studio Code Samples.
//
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//-----------------------------------------------------------------------

using W3C.Soap;
using Microsoft.Ccr.Core;
using Microsoft.Ccr.Adapters.WinForms;
using Microsoft.Dss.Core;
using Microsoft.Dss.Core.Attributes;
using Microsoft.Dss.ServiceModel.Dssp;
using Microsoft.Dss.ServiceModel.DsspServiceBase;

using System;
using System.Collections.Generic;

using Microsoft.Robotics.Simulation;
using Microsoft.Robotics.Simulation.Engine;
using engineproxy = Microsoft.Robotics.Simulation.Engine.Proxy;
using Microsoft.Robotics.Simulation.Physics;
using Microsoft.Robotics.PhysicalModel;
using System.ComponentModel;

using corobot = ProMRDS.Simulation.Corobot;

namespace ProMRDS.BallCourt
{
    [DisplayName("BallCourt")]
    [Description("A ball court for FollowBall")]
    [Contract(Contract.Identifier)]
    public class BallCourt : DsspServiceBase
    {
        [InitialStatePartner(Optional = true, ServiceUri = "ProMRDS/Config/BallCourt.Config.xml")]
        BallCourtState _state = new BallCourtState();

        [Partner("Engine",
            Contract = engineproxy.Contract.Identifier,
            CreationPolicy = PartnerCreationPolicy.UseExistingOrCreate)]
        private engineproxy.SimulationEnginePort _engineStub =
            new engineproxy.SimulationEnginePort();

        // Main service port
        [ServicePort("/BallCourt", AllowMultipleInstances=false)]
        private BallCourtOperations _mainPort =
            new BallCourtOperations();

        public BallCourt(DsspServiceCreationPort creationPort) :
                base(creationPort)
        {
        }

        protected override void Start()
        {
            if (_state == null)
                CreateDefaultState();

            base.Start();

            // Orient sim camera view point
            SetupCamera();
            // Add objects (entities) in our simulated world
            PopulateWorld();
            // set the bounce threshold
            SpawnIterator(SetBounceThreshold);
        }


        /// <summary>
        /// Set up the default configuration
        /// </summary>
        void CreateDefaultState()
        {
            _state = new BallCourtState();

            _state.BallSpeed = 0.2f;
            _state.Scale = 1.0f;

            SaveState(_state);
        }

        private void SetupCamera()
        {
            // Set up initial view
            CameraView view = new CameraView();
            view.EyePosition = new Vector3(-1.65f, 1.63f, -0.29f);
            view.LookAtPoint = new Vector3(-0.68f, 1.38f, -0.18f);
            SimulationEngine.GlobalInstancePort.Update(view);
        }
        
        IEnumerator<ITask> SetBounceThreshold()
        {
            while (PhysicsEngine.GlobalInstance == null)
                yield return Arbiter.Receive(false, TimeoutPort(100), delegate(DateTime now) { });

            PhysicsEngine.GlobalInstance.BounceThreshold = (float)-0.25;
        }

        private void PopulateWorld()
        {
            AddSky();
            AddGround();
            BuildArena();
            // create a Corobot
            SimulationEngine.GlobalInstancePort.Insert(new corobot.CorobotEntity("Corobot", new Vector3(2, 0, 0)));
            Activate(Arbiter.Receive(false, TimeoutPort(5000), AddBall));
        }

        void BuildArena()
        {
            MaterialProperties bouncyMaterial = new MaterialProperties("Bouncy", 1.0f, 0.5f, 0.6f);
            MaterialProperties SlickMaterial = new MaterialProperties("Slick", 1.0f, 0.0f, 0.0f);
            BoxShape[] boxShapes = new BoxShape[]
            {
                new BoxShape(new BoxShapeProperties(0, 
                    new Pose(new Vector3(0, 0.5f*_state.Scale, -4*_state.Scale)), 
                    new Vector3(12*_state.Scale, 1*_state.Scale, 0.25f*_state.Scale))),
                new BoxShape(new BoxShapeProperties(0, 
                    new Pose(new Vector3(0, 0.5f*_state.Scale, 4*_state.Scale)), 
                    new Vector3(12*_state.Scale, 1*_state.Scale, 0.25f*_state.Scale))),
                new BoxShape(new BoxShapeProperties(0, 
                    new Pose(new Vector3(-(6f - 0.125f)*_state.Scale, 0.5f*_state.Scale, 0)), 
                    new Vector3(0.25f*_state.Scale, 1*_state.Scale, 8f*_state.Scale))),
                new BoxShape(new BoxShapeProperties(0, 
                    new Pose(new Vector3( (6f - 0.125f)*_state.Scale, 0.5f*_state.Scale, 0)), 
                    new Vector3(0.25f*_state.Scale, 1*_state.Scale, 8f*_state.Scale))),
            };
            boxShapes[0].State.Material = bouncyMaterial;
            boxShapes[1].State.Material = bouncyMaterial;
            boxShapes[2].State.Material = bouncyMaterial;
            boxShapes[3].State.Material = bouncyMaterial;

            MultiShapeEntity arena = new MultiShapeEntity(boxShapes, null);
            arena.State.Name = "BallCourt";
            arena.State.Assets.DefaultTexture = "BrickWall.dds";
            SimulationEngine.GlobalInstancePort.Insert(arena);
        }

        void AddBall(DateTime now)
        {
            MaterialProperties SlickMaterial = new MaterialProperties("Slick", 1.0f, 0.0f, 0.0f);
            SphereShape ballShape = new SphereShape(new SphereShapeProperties(0.1f, new Pose(), 0.1f * _state.Scale));
            ballShape.State.Material = SlickMaterial;
            ballShape.State.DiffuseColor = new Vector4(1, 0, 0, 1);
            SingleShapeEntity Ball = new SingleShapeEntity(
                ballShape,
                new Vector3());
            Ball.State.Name = "Ball";
            Ball.State.Velocity = new Vector3(_state.BallSpeed * _state.Scale, 0, -_state.BallSpeed * _state.Scale);
            Ball.State.Pose.Position = new Vector3(0, 0, 0.5f);
            SimulationEngine.GlobalInstancePort.Insert(Ball);
        }

        void AddSky()
        {
            // Add a sky using a static texture. We will use the sky texture
            // to do per pixel lighting on each simulation visual entity
            SkyDomeEntity sky = new SkyDomeEntity("skydome.dds", "sky_diff.dds");
            SimulationEngine.GlobalInstancePort.Insert(sky);

            // Add a directional light to simulate the sun.
            LightSourceEntity sun = new LightSourceEntity();
            sun.State.Name = "Sun";
            sun.Type = LightSourceEntityType.Directional;
            sun.Color = new Vector4(0.8f, 0.8f, 0.8f, 1);
            sun.Direction = new Vector3(0.5f, -.75f, 0.5f);
            SimulationEngine.GlobalInstancePort.Insert(sun);
        }

        void AddGround()
        {
            // Create a slab on which the arena rests
            BoxShape groundShape = new BoxShape(new BoxShapeProperties(
                0, new Pose(), new Vector3(24, 1, 16)));
            SingleShapeEntity ground = new SingleShapeEntity(groundShape, new Vector3(0, 0, 0));
            ground.State.Name = "Ground Slab";
            ground.State.Pose = new Pose(new Vector3(0, -0.5f, 0));
            ground.State.Assets.DefaultTexture = "BrickWall.dds";
            groundShape.State.DiffuseColor = new Vector4(0.1f, 0.1f, 0.1f, 1);
            groundShape.State.Material = new MaterialProperties("ground",
                    0.2f, // restitution
                    0.5f, // dynamic friction
                    0.5f);// static friction
            SimulationEngine.GlobalInstancePort.Insert(ground);
        }
    }

    public static class Contract
    {
        public const string Identifier = "http://www.promrds.com/contracts/2008/01/ballcourt.html";
    }

    [ServicePort]
    public class BallCourtOperations : PortSet<DsspDefaultLookup, DsspDefaultDrop>
    {
    }

    [DataContract]
    public class BallCourtState
    {
        [DataMember]
        public float BallSpeed;

        [DataMember]
        public float Scale;
    }
}
